#pragma once
#include <iostream>
#include <stdio.h>
#include <mutex>

namespace ns_smartptr
{
    template <class T>
    class auto_ptr
    {
    public:
        auto_ptr(T* ptr) : _ptr(ptr)
        {}

        auto_ptr(auto_ptr<T>& ap) : _ptr(ap._ptr)
        {
            ap._ptr = nullptr;
        }

        T& operator*()
        {
            return *_ptr;
        }

        T* operator->()
        {
            return _ptr;
        }

        T* get() const
        {
            return _ptr;
        }

        ~auto_ptr()
        {
            if(_ptr)
            {
                std::cout << "delete _ptr" << std::endl;
                delete _ptr;
            }
        }
    private:
        T* _ptr;
    };

    template <class T>
    struct default_del
    {
        void operator()(const T* ptr)
        {
            std::cout << "delete ptr" << std::endl;
            delete ptr;
        }
    };

    template <class T>
    struct DeleteArray
    {
        void operator()(const T* ptr)
        {
            std::cout << "delete[] ptr" << std::endl;
            delete[] ptr;
        }
    };

    struct DeleteFile
    {
        void operator()(FILE* fp)
        {
            std::cout << "fclose file" << std::endl;
            fclose(fp);
        }
    };

    template <class T, class D = default_del<T>>
    class unique_ptr
    {
    public:
        unique_ptr(T* ptr) : _ptr(ptr)
        {}

        unique_ptr(const unique_ptr<T>& up) = delete;
        unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;

        T& operator*()
        {
            return *_ptr;
        }

        T* operator->()
        {
            return _ptr;
        }

        T* get() const
        {
            return _ptr;
        }

        ~unique_ptr()
        {
            if(_ptr)
            {
                //std::cout << "delete _ptr" << std::endl;
                //delete _ptr;
                D d;
                d(get());
            }
        }
    private:
        T* _ptr;
    };

    template <class T>
    class shared_ptr
    {
    public:
        shared_ptr(T* ptr = nullptr) : _ptr(ptr), _pRefCount(new int(1)), _pmtx(new std::mutex)
        {}

        shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx)
        {
            AddRef();
        }

        shared_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
            if(_ptr != sp._ptr)
            {
                Release();
                _ptr = sp._ptr;
                _pmtx = sp._pmtx;
                _pRefCount = sp._pRefCount;
                AddRef();
            }

            return *this;
        }

        void Release()
        {
            _pmtx->lock();
            bool flag = false;
            if(--(*_pRefCount) == 0 && _ptr)
            {
                std::cout << "delete _ptr" << std::endl;
                delete _ptr;
                delete _pRefCount;
                flag = true;
            }
            _pmtx->unlock();
            if(flag)
                delete _pmtx;
        }

        void AddRef()
        {
            _pmtx->lock();
            ++(*_pRefCount);
            _pmtx->unlock();
        }

        T& operator*()
        {
            return *_ptr;
        }

        T* operator->()
        {
            return _ptr;
        }

        T* get() const
        {
            return _ptr;
        }

        size_t use_count() const
        {
            return *_pRefCount;
        }

        ~shared_ptr()
        {
            Release();
            _ptr = nullptr;
        }
    private:
        T* _ptr;
        int* _pRefCount;
        std::mutex* _pmtx;
    };

    template <class T>
    class weak_ptr
    {
    public:
        weak_ptr() : _ptr(nullptr)
        {}

        weak_ptr(const shared_ptr<T>& sp) : _ptr(sp.get())
        {}

        weak_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
            if(_ptr != sp.get())
            {
                _ptr = sp.get();
            }

            return *this;
        }

        T& operator*()
        {
            return *_ptr;
        }

        T* operator->()
        {
            return _ptr;
        }
    private:
        T* _ptr;
    };

    void Test()
    {
        unique_ptr<int> up(new int);
        *up = 10;
        std::cout << *up << std::endl;

        unique_ptr<int, DeleteArray<int>> up1(new int[5]);

        unique_ptr<FILE, DeleteFile> up2(fopen("test.c", "w"));
        
    }
}